package com.easyaop.test.core.pointcut;

import com.easyaop.commons.MethodSignature;

import net.bytebuddy.description.NamedElement;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author lipan
 * @since 2025-01-21
 */
public class MethodPointcut extends AbstractPointcutEventListener {
    private final String className;

    private final MethodSignature methodSignature;

    private final String signature;

    protected MethodPointcut(String className, MethodSignature methodSignature) {
        if (className == null || className.length() == 0) {
            throw new IllegalArgumentException("className is null");
        }
        if (methodSignature == null) {
            throw new IllegalArgumentException("methodSignature is null");
        }
        this.className = className;
        this.methodSignature = methodSignature;
        this.signature = className + "#" + methodSignature;
    }

    protected MethodPointcut(String className, String methodName, String methodDesc) {
        this(className, new MethodSignature(methodName, methodDesc));
    }

    public String getClassName() {
        return className;
    }

    public MethodSignature getMethodSignature() {
        return methodSignature;
    }

    public String getMethodName() {
        return methodSignature.getName();
    }

    public String getMethodDescriptor() {
        return methodSignature.getDescriptor();
    }

    public String getSignature() {
        return signature;
    }

    public ElementMatcher<NamedElement> getClassNameMatcher() {
        return ElementMatchers.named(className);
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof MethodPointcut && equals((MethodPointcut) obj);
    }

    public boolean equals(MethodPointcut other) {
        return className.equals(other.className) && Objects.equals(methodSignature, other.methodSignature);
    }

    @Override
    public int hashCode() {
        return className.hashCode() ^ methodSignature.hashCode();
    }

    @Override
    public String toString() {
        return className + "#" + methodSignature;
    }

    public static MethodPointcut of(Method method) {
        return new MethodPointcut(method.getDeclaringClass().getName(), new MethodSignature(method));
    }

    public static MethodPointcut forMethodPointcut(String className, String methodName, String methodDesc) {
        String signature = className + "#" + methodName + methodDesc;
        return CACHE.computeIfAbsent(signature, (key) -> new MethodPointcut(className, methodName, methodDesc));
    }

    public static MethodPointcut forMethodPointcut(Class<?> target, String methodName, Class<?> rType,
                                                   Class<?>... pTypes) {
        return forMethodPointcut(target.getName(), methodName, MethodSignature.getDescriptor(rType, pTypes));
    }

    public static MethodPointcut findMethodPointcut(String signature) {
        if (CACHE.containsKey(signature)) {
            return CACHE.get(signature);
        }
        throw new IllegalArgumentException("not such methodPointcut");
    }
}
